﻿// ===============================================================================
// XCI.NET 西安交通信息WinForm开发框架 2018 版权所有
// ===============================================================================
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using XCI.Core;
using XCI.Extensions;
using XCI.Helper;

namespace XCI.Logger
{
    /// <summary>
    /// 文件日志记录实现
    /// </summary>
    public class FileLogger : BaseLogger, IDisposable
    {
        #region 私有变量

        private const string ExtName = ".log";
        private System.Timers.Timer deleteFileTimer;
        private System.Timers.Timer queueFileTimer;
        private BlockingCollection<LoggerInfo> addQueue;
        private BlockingCollection<List<LoggerInfo>> writeQueue;
        private List<LoggerInfo> cache;
        private FileLoggerStat stat;

        #endregion

        #region 构造函数

        /// <summary>
        /// 初始化文件日志记录新实例
        /// </summary>
        /// <param name="config">文件配置</param>
        public FileLogger(FileLoggerConfig config)
        {
            Config = config;
            Level = Config.Level;
            stat = new FileLoggerStat();
            addQueue = new BlockingCollection<LoggerInfo>(Config.QueueBufferSize);
            writeQueue = new BlockingCollection<List<LoggerInfo>>(Config.QueueBufferSize / 2);
            cache = new List<LoggerInfo>(Config.WriteBufferSize);

            DeleteAcion();
            InitQueueTimer();
            Task.Factory.StartNew(AddQueueProcess, TaskCreationOptions.LongRunning);
            Task.Factory.StartNew(WriteQueueProcess, TaskCreationOptions.LongRunning);
        }

        #endregion

        #region 公共属性

        /// <summary>
        /// 文件日志配置对象
        /// </summary>
        public FileLoggerConfig Config { get; set; }

        #endregion

        #region 公共方法

        /// <summary>
        /// 获取统计信息
        /// </summary>
        /// <returns></returns>
        public FileLoggerStat GetStat()
        {
            stat.Cache = cache.Count;
            return stat;
        }

        #endregion

        #region 队列相关操作

        /// <summary>
        /// 后台扫描队列处理
        /// </summary>
        private void AddQueueProcess()
        {
            foreach (var item in addQueue.GetConsumingEnumerable())
            {
                stat.AddTotal++;
                cache.Add(item);
                queueFileTimer.Start();
                //DebugHelper.Debug("消费日志队列,开启写入计时器,把日志消息加入缓存中");
                if (cache.Count == Config.WriteBufferSize)
                {
                    //DebugHelper.Debug($"当前缓存已满,停止写入计时器,创建新的缓存并把已满的缓存加入写队列中");
                    queueFileTimer.Stop();
                    AddCacheToWriteQueue();
                }
            }
        }

        /// <summary>
        /// 写文件队列处理
        /// </summary>
        private void WriteQueueProcess()
        {
            foreach (var item in writeQueue.GetConsumingEnumerable())
            {
                stat.WriteCount++;
                WriteLog(item);
            }
        }

        /// <summary>
        /// 初始化缓存写入超时计时器
        /// </summary>
        private void InitQueueTimer()
        {
            queueFileTimer = new System.Timers.Timer();
            queueFileTimer.Interval = Config.WriteInterval.TotalMilliseconds;
            queueFileTimer.AutoReset = false;
            queueFileTimer.Elapsed += (o, e) =>
            {
                DebugHelper.Debug("缓存写入超时时间已到,创建新的缓存并把已满的缓存加入写队列中");
                AddCacheToWriteQueue();
            };
        }

        /// <summary>
        /// 缓存切换
        /// </summary>
        private void AddCacheToWriteQueue()
        {
            var oldCache = cache;
            cache = new List<LoggerInfo>(Config.WriteBufferSize);
            writeQueue.Add(oldCache);
        }

        #endregion

        #region 日志文件创建相关操作

        /// <summary>
        /// 获取日志文件写入目录
        /// </summary>
        private string GetLogDir()
        {
            var dir = Config.Dir;
            var dirName = CastVariable(dir, v =>
            {
                if (v.Equals("root", StringComparison.OrdinalIgnoreCase))
                {
                    return IOHelper.RootDirectory.TrimEnd('\\');
                }
                return DateTime.Now.ToString(v);
            });
            return dirName;
        }

        /// <summary>
        /// 获取日志文件名称
        /// </summary>
        private string GetLogFileName()
        {
            var name = Config.FileName;
            var fileName = CastVariable(name, v => DateTime.Now.ToString(v));
            return fileName;
        }

        /// <summary>
        /// 获取日志文件路径
        /// </summary>
        public string GetFilePath()
        {
            Guard.NotNullOrWhiteSpace(Config.Dir, nameof(Config.Dir));
            Guard.NotNullOrWhiteSpace(Config.FileName, nameof(Config.FileName));
            var dirName = GetLogDir();
            var fileName = GetLogFileName();
            var path = Path.Combine(dirName, fileName + ExtName);
            if (HasLimit(path))
            {
                path = GetSplitFilePath(path);
                //DebugHelper.Debug($"获取写文件路径,当前文件已经超过规定的大小,重新分割文件,文件:{path}");
            }
            IOHelper.CreateDirectoryByPath(path);
            return path;
        }

        /// <summary>
        /// 检测文件大小是否超过设置值
        /// </summary>
        /// <param name="path">文件路径</param>
        /// <returns>超过设置值，返回true，否则返回false。</returns>
        private bool HasLimit(string path)
        {
            if (!File.Exists(path)) return false;
            if (Config.FileSizeLimit == 0) return false;//不限制

            var fileInfo = new FileInfo(path);
            var fileSizeKb = fileInfo.Length / 1024;
            return fileSizeKb >= Config.FileSizeLimit;
        }

        /// <summary>
        /// 获取分割文件路径
        /// </summary>
        /// <param name="currentPath">当前文件路径</param>
        /// <returns>返回分割后的文件路径</returns>
        private string GetSplitFilePath(string currentPath)
        {
            const string spliter = "-";
            var fileName = Path.GetFileNameWithoutExtension(currentPath);
            var dirName = Path.GetDirectoryName(currentPath) ?? GetLogDir();
            var files = Directory.GetFiles(dirName, fileName + spliter + "*" + ExtName);
            var max = 1;
            foreach (var it in files)
            {
                var name = Path.GetFileNameWithoutExtension(it);
                var index = name.LastIndexOf(spliter, StringComparison.OrdinalIgnoreCase);
                if (index < -1) continue;
                var num = name.Substring(index + 1).ToInt();
                if (num > max)
                {
                    max = num;
                }
            }
            var tmpFileName = fileName + spliter + max;
            if (HasLimit(Path.Combine(dirName, tmpFileName + ExtName)))
            {
                max++;
                fileName += spliter + max;
            }
            else
            {
                fileName = tmpFileName;
            }
            return Path.Combine(dirName, fileName + ExtName);
        }

        /// <summary>
        /// 输出缓冲区中的日志信息
        /// </summary>
        private void WriteLog(IList<LoggerInfo> logs)
        {
            try
            {
                var path = GetFilePath();
                //DebugHelper.Debug($"写日志文件线程开始,文件:{path},记录数:{logs.Count}");
                using (var streamWriter = new StreamWriter(path, true))
                {
                    foreach (var info in logs)
                    {
                        stat.WriteTotal++;
                        streamWriter.WriteLine(CastLogInfo(Config.MessageTemplate, info));
                    }
                }
                logs.Clear();
            }
            catch (Exception e)
            {
                DebugHelper.Catch(e.Message);
            }
        }

        #endregion

        #region 处理过期文件相关操作

        /// <summary>
        /// 处理过期文件相关操作
        /// </summary>
        private void DeleteAcion()
        {
            if (!Config.AutoDelete) return;
            DeleteExpireFile();
            deleteFileTimer = new System.Timers.Timer();
            deleteFileTimer.Interval = Config.AutoDeleteInterval.TotalMilliseconds;
            deleteFileTimer.AutoReset = false;
            deleteFileTimer.Elapsed += (o, e) => DeleteExpireFile();
        }

        /// <summary>
        /// 删除过期的日志文件(只删除后者名称为.log的日志文件)
        /// </summary>
        private void DeleteExpireFile()
        {
            void Acition()
            {
                try
                {
                    DebugHelper.Debug("清理过期日志文件线程开始");
                    var fileList = new List<string>();
                    var dir = GetLogDir();
                    var logFileList = IOHelper.GetFiles(dir);
                    var minDate = DateTime.Now.Subtract(Config.AutoDeleteTimeSpan);
                    foreach (var file in logFileList)
                    {
                        var ext = Path.GetExtension(file);
                        if (!string.IsNullOrEmpty(ext) && ext.ToLower().Equals(".log"))
                        {
                            var fileInfo = new FileInfo(file);
                            if (fileInfo.CreationTime <= minDate)
                            {
                                fileList.Add(file);
                                DebugHelper.Debug($"待删除的过期日志文件:{file}");
                            }
                        }
                    }
                    fileList.ForEach(IOHelper.DeleteFile);
                }
                catch (Exception e)
                {
                    DebugHelper.Catch(e.Message);
                }
            }

            Task.Factory.StartNew(Acition).ContinueWith(t => deleteFileTimer.Start());
        }

        #endregion

        #region BaseLogger

        /// <summary>
        /// 日志实现
        /// </summary>
        /// <param name="logInfo">日志消息</param>
        protected override void LogCore(LoggerInfo logInfo)
        {
            addQueue.Add(logInfo);
        }

        #endregion

        #region IDisposable

        /// <summary>
        /// 销毁类对象
        /// </summary>
        public void Dispose()
        {
            AddCacheToWriteQueue();
        }

        /// <summary>
        /// 析构函数
        /// </summary>
        ~FileLogger()
        {
            AddCacheToWriteQueue();
        }

        #endregion
    }
}